[REVIEW] New Dataset API Clarifying Ownership#1846
Conversation
|
/ok to test 5447a4c |
|
/ok to test 17ab09d |
|
NB: I updated the label to |
@achirkin The problem w/ using mdspan/mdarray for this is that it's not carrying along the proper information to either the algorithms nor the user (which is why we created this specialized class for this in the first place!). Two immediate reasons why this API is necessary:
This new API solves both of these problems while leaving the control over the memory ownership entirely in the user's hands. We've discussed this for a long time. We've known this is needed for a long time. it's time to prioritize this and get it done. I agree that an anstract class might make more sense, but ultimately we should not be moving any owneship over to the algorithm (the user should maintain ownership over the class and underlying memory the entire time). |
…tion between make host/device padded dataset in factory
… of dataset + create build_result struct which returns both index and vpq_dataset to prevent automatic out of scope destruction of dataset for vpq case
…rt for cases where we DO need to own the dataset (in order to keep view alive for index). All cases where we build() from dataset already on device --> we don't need to own. Merge + All cases when data is on host --> we DO need to own the device copy we create. This includes within ACE build and C API build from host and from_args with host dataset
|
The doc that outlines some of the API design choices can be found in slack. Let me know if there are any parts of the design that can be altered to better suit our users' needs. The following files are test case files I've added and can be ignored for now. They will be removed before the final merge with upstream repo:
|
…e but internally convert it to a device_padded_dataset because iterative build is the exception which calls search and search kernels need correct padding and search can only happen on device. Remove cagra_dataset_view_dispatch.hpp file
…e but internally convert it to a device_padded_dataset because iterative build is the exception which calls search and search kernels need correct padding and search can only happen on device. Remove cagra_dataset_view_dispatch.hpp file
…w standard_dataset. This recovers functionality that is present in upstream main to support serialize/deserialize and merging of non-padded datasets
…sion not to be publically called but to be used inside deserialize
|
/ok to test 368d058 |
…is reduces the number of total containerTypes by half
…o distinguish host vs device. Rewrite conversion between host to device counterpart using a single mapping function rather than ad-hoc polymorphism
…ng visibility errors causing failing KMEANS_MG_C_TEST and CAGRA_C_TEST. This failure was caused by a commit from upsream main being merged in
divyegala
left a comment
There was a problem hiding this comment.
I really like the new dataset design. My major open question now is about the C-API assuming that CAGRA indexes are built with device_padded_dataset
| * then call `update_dataset(res, std::move(*stolen_fd))` on the target device index. | ||
| * Clears the stored fd (and leaves n_rows_/dim_ in place for the remaining graph). | ||
| */ | ||
| [[nodiscard]] inline auto steal_dataset_fd() noexcept |
There was a problem hiding this comment.
+1, is this intended to be used by end-users?
| template <typename DatasetViewT> | ||
| requires(!cuvs::neighbors::is_empty_dataset_view_v<DatasetViewT> && | ||
| (cuvs::neighbors::is_device_dataset_view_v<DatasetViewT> || | ||
| cuvs::neighbors::is_host_dataset_view_v<DatasetViewT>)) | ||
| auto build(raft::resources const& res, |
| std::optional<raft::device_matrix_view<float, int64_t, raft::layout_stride>> | ||
| new_dataset_buffer_view = std::nullopt, | ||
| std::optional<raft::device_matrix_view<uint32_t, int64_t>> new_graph_buffer_view = std::nullopt); | ||
|
|
||
| /** @brief Add new vectors to a CAGRA index |
There was a problem hiding this comment.
Why are all the extend docs removed?
| if constexpr (cuvs::neighbors::is_device_padded_dataset_view_v<DatasetViewT>) { | ||
| auto v = raft::make_device_matrix_view<const T, int64_t>( | ||
| static_cast<const T*>(nullptr), int64_t{0}, dim_); | ||
| dataset_ = DatasetViewT(v, dim_); | ||
| } else if constexpr (cuvs::neighbors::is_host_padded_dataset_view_v<DatasetViewT>) { | ||
| auto v = raft::make_host_matrix_view<const T, int64_t>( | ||
| static_cast<const T*>(nullptr), int64_t{0}, dim_); | ||
| dataset_ = DatasetViewT(v, dim_); | ||
| } else if constexpr (cuvs::neighbors::is_empty_dataset_view_v<DatasetViewT>) { |
There was a problem hiding this comment.
Let's capture this in an issue and fix in a follow-up PR, I think we can have ACE to work with a different dataset type that is ACE specific to remove any confusions.
| auto build_params = tiered_index::index_params<cagra::index_params>(); | ||
| convert_c_index_params(params, dataset.shape[0], dataset.shape[1], &build_params); | ||
| return new tiered_index::index<cagra::index<T, uint32_t>>( | ||
| return new tiered_index::index<cagra::device_padded_index<T, uint32_t>>( |
There was a problem hiding this comment.
Same question as above. We are not enforcing that the DLPack managed tensor be a device padded type
| * vs view. | ||
| */ | ||
|
|
||
| template <typename containertype, typename DataT, typename IdxT, typename Accessor> |
There was a problem hiding this comment.
| template <typename containertype, typename DataT, typename IdxT, typename Accessor> | |
| template <typename ContainerType, typename DataT, typename IdxT, typename Accessor> |
| template <typename containertype, typename DataT, typename IdxT, typename Accessor> | ||
| struct dataset; | ||
|
|
||
| template <typename containertype, typename DataT, typename IdxT, typename Accessor> |
There was a problem hiding this comment.
| template <typename containertype, typename DataT, typename IdxT, typename Accessor> | |
| template <typename ContainerType, typename DataT, typename IdxT, typename Accessor> |
| // ----------------------------------------------------------------------------- | ||
| // padded (row-major with logical dim vs stride) | ||
| // ----------------------------------------------------------------------------- |
| // dense row-major (logical dim may differ from row pitch; shared by padded & standard) | ||
| // ----------------------------------------------------------------------------- | ||
|
|
||
| template <typename MatrixT, typename ViewT, typename DataT, typename IdxT> |
There was a problem hiding this comment.
Document what the templates mean
…ard_dataset build was enabled in cpp layer but not at C API layer
…o use data() (DatasetViewT) directly instead of relying on a dataset() legacy bridge to continue supporting device_matrix_view
| } | ||
|
|
||
| template <class T, class IdxT, class Accessor> | ||
| template <class T, class IdxT, cuvs::neighbors::cagra_dataset_view DatasetViewT, class Accessor> |
There was a problem hiding this comment.
Why do we need a separate dataset for CAGRA specifically? We want to keep this general, right? We shouldn't need new dataset types for every index type.
|
|
||
| // Default owning/view accessors for public dataset aliases. | ||
| template <typename T> | ||
| using device_owning_accessor = raft::device_accessor<raft::device_container_policy<T>>; |
There was a problem hiding this comment.
Can we split this stuff out into include/core/dataset.hpp please? The dataset APIs are a common vocabulary type and it would be good to have them in core/ instead of neighbors/.
This way the quantize APIs can provide their dataset types and we can avoid any awkward circular references for files outside of neighbors/ to have to include or otherwise reference types within neighbors/.
I see it like this:
core/datasets.hpp -> core dataset types (for e.g. strided, empty, standard), factories, and aliasa
preprocessing/quantize/pq.hpp -> vpq_dataset types, factories, and aliases
preprocessing/quantize/rabitq.hpp -> rabitq_dataset types, factories, and aliases
neighbors/cagra.hpp -> uses datasets from core and preprocessing.
The layout above avoids circular dependencies across namespaces. If we don't do this, we'll ultimately have vpq_dataset in preprocessing/quantize depending upon dataset abstractions in neighbors/common.hpp and that makes the dependency tree awkward (as it's no longer a tree, but a graph).
|
/ok to test 1aeeb85 |
…r chain in index() within cagra.hpp
Overview
Addressing #1574 and #1571.
Replaced strided_dataset with padded_dataset class. Added support all the way up to CAGRA code.
Old class structure (Classes + Inheritance):
New Class Structure (ContainerType Tags + Composition):
Inheritance is removed entirely and all dataset types are on the same level of the inheritance tree.
3 Levels:
Ownership
The index and cagra::build / cagra::index do not own raw vector storage, they only take views.
The old code had a type-erased std::unique_ptr<dataset_view<...>>, i.e. non-owning view handles. The new code uses templates on the index type which determines the type of dataset_view the index holds.
ACE v.s. non-ACE paths on Host
ACE path copies datasets that can't entirely fit in CPU memory in chunks onto GPU memory by calling make_padded_dataset. This is 1x memory on CPU and 1x memory on GPU.
Return types:
Used mainly to maintain lifetime of dataset.
cuvs_cagra_c_api_lifetime_holder
It is a single C++ struct in cagra.cpp that groups the real cagra::index with any extra heap-owned things the C API had to create so the index’s non-owning views stay valid.
Miscellaneous: Extend Serialize Deserialize
Will fill in later
Factories:
Places where make_padded_dataset/view are called internally (not by user):
Host non-ACE path
Tiered CAGRA
Ownership in Downstream Functions:
Improvements:
Breaking Changes for Dataset API:
The following functions are removed since index no longer owns the dataset, index only takes views:
Removed old functions that took mdspan or derivatives of mdspan.
4 cases where index previously owned dataset [all deprecated paths]:
2 edge case build() paths when attach_dataset_on_build == true and a successful dense attach:
Compression Param:
Merge:
These paths have since been removed.
Attach Dataset
Compressed Dataset
Merged Dataset
Deserialize
Helpers
How to attach a compressed dataset onto an uncompressed index?
How to attach a searchable device dataset onto an index built with host build?
a. Utilizes map of host dataset type to device dataset type counterpart
TODOs:
Recent Updates:
Future PRs:
PR#2: Add Support for Compressed Datasets
PR#3: Migrate Rest of Algorithms to use Dataset API